/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.wangxiandeng.swipecardrecyclerview;

import ohos.agp.animation.Animator;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.utils.Point;
import ohos.multimodalinput.event.MmiPoint;
import ohos.multimodalinput.event.TouchEvent;

public class FlingCardListener implements Component.TouchEventListener {
  private static final int INVALID_POINTER_ID = -1;

  private final float mObjectX;
  private final float mObjectY;
  private final int mObjectH;
  private final int mObjectW;
  private final int mParentWidth;
  private final FlingListener mFlingListener;
  private final Object mDataObject;
  private final float mHalfWidth;
  private float BASE_ROTATION_DEGREES;

  private float mPosX;
  private float mPosY;
  private float mDownTouchX;
  private float mDownTouchY;

  // The active pointer is the one currently moving our object.
  private int mActivePointerId = INVALID_POINTER_ID;
  private Component mFrame = null;
  private ComponentContainer componentContainer=null;

  private final int TOUCH_ABOVE = 0;
  private final int TOUCH_BELOW = 1;
  private int mTouchPosition;
  private final Object obj = new Object();
  private boolean mIsAnimationRunning = false;
  private float MAX_COS = (float) Math.cos(Math.toRadians(45));
  private int slideType = 0;
  private final int SLIDETYPE_ACEOSS = 1;
  private final int SLIDETYPE_lVERTICAL = 2;
  private boolean isSlide = true;
  private boolean imageSpin = true;

  public FlingCardListener(
      ComponentContainer parent, int slideType, Component frame, boolean imageSpin,
      Object itemAtPosition, FlingListener flingListener) {
    this(parent, slideType, frame, imageSpin, itemAtPosition, 15f, flingListener);
  }

  public FlingCardListener(
      ComponentContainer parent, int slideType,
      Component frame, boolean imageSpin,
      Object itemAtPosition,
      float rotation_degrees,
      FlingListener flingListener) {
    super();
    this.mFrame = frame;
    this.slideType = slideType;
    this.mObjectX = frame.getContentPositionX();
    this.mObjectY = frame.getContentPositionY();
    this.mObjectH = frame.getHeight();
    this.mObjectW = frame.getWidth();
    this.mHalfWidth = mObjectW / 2f;
    this.imageSpin = imageSpin;
    this.mDataObject = itemAtPosition;
    this.mParentWidth = parent.getWidth();
    this.BASE_ROTATION_DEGREES = rotation_degrees;
    this.mFlingListener = flingListener;
  }

  public void setCannotSlide(boolean isSlide) {
    this.isSlide = isSlide;
  }

 public void setComponentContainer(ComponentContainer componentContainer){
    this.componentContainer=componentContainer;
 }
    private float mTopViewX;
  @Override
  public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
    MmiPoint point = touchEvent.getPointerScreenPosition(0);
    if (isSlide) {
      switch (touchEvent.getAction()) {
        case TouchEvent.PRIMARY_POINT_DOWN:
          float downTouchX = point.getX();
          float downTouchY = point.getY();
          mDownTouchX = downTouchX;
          mDownTouchY = downTouchY;

          // to prevent an initial jump of the magnifier, aposX and aPosY must
          // have the values from the magnifier frame
          if (mPosX == 0) {
            mPosX = mFrame.getContentPositionX();
          }
          if (mPosY == 0) {
            mPosY = mFrame.getContentPositionY();
          }

          if (downTouchY - mFrame.getContentPositionY() < mObjectH / 2f) {
            mTouchPosition = TOUCH_ABOVE;
          } else {
            mTouchPosition = TOUCH_BELOW;
          }
          break;
        case TouchEvent.PRIMARY_POINT_UP:
          mActivePointerId = INVALID_POINTER_ID;
          resetCardViewOnStack();
          break;
        case TouchEvent.OTHER_POINT_DOWN:
          break;
        case TouchEvent.OTHER_POINT_UP:
          break;
        case TouchEvent.POINT_MOVE:
          // Find the index of the active pointer and fetch its position
          if (slideType == SLIDETYPE_ACEOSS) {
            final float moveTouchX = point.getX();

            // Calculate the distance moved
            final float dx = moveTouchX - mDownTouchX;

            // Move the frame
            mPosX += dx;

            // calculate the rotation degrees
            float distobjectX = mPosX - mObjectX;

            float rotation = BASE_ROTATION_DEGREES * 2.f * distobjectX / mParentWidth;

            // move the component
            mFrame.setTranslationX(mFrame.getContentPositionX() + dx);
            mFrame.setRotation(imageSpin ? rotation : 0);
            mFlingListener.onScroll(getScrollProgressPercent());

            // save the touch point position
            mDownTouchX = moveTouchX;
          } else if (slideType == SLIDETYPE_lVERTICAL) {
            final float moveTouchY = point.getY();

            // Calculate the distance moved
            final float dy = moveTouchY - mDownTouchY;

            // Move the frame
            mPosY += dy;

            // calculate the rotation degrees
            float distobjectX = mPosX - mObjectX;

            float rotation = BASE_ROTATION_DEGREES * 2.f * distobjectX / mParentWidth;

            // move the component
            mFrame.setTranslationY(mFrame.getContentPositionY() + dy);
            mFrame.setRotation(imageSpin ? rotation : 0);
            mFlingListener.onScroll(getScrollProgressPercent());

            // save the touch point position
            mDownTouchY = moveTouchY;
          } else {
            final float moveTouchX = point.getX();
            final float moveTouchY = point.getY();

            // Calculate the distance moved
            final float dx = moveTouchX - mDownTouchX;
            final float dy = moveTouchY - mDownTouchY;

            // Move the frame
            mPosX += dx;
            mPosY += dy;

            // calculate the rotation degrees
            float distobjectX = mPosX - mObjectX;

            float rotation = BASE_ROTATION_DEGREES * 2.f * distobjectX / mParentWidth;

            // move the component

            mFrame.setPosition((int)(mFrame.getContentPositionX() + dx),(int)(mFrame.getContentPositionY() + dy));
            updateNextItem(Math.abs(mPosX - mObjectX) * 0.2/120  + 0.8);

            mFrame.setRotation(imageSpin ? rotation : 0);
            mFlingListener.onScroll(getScrollProgressPercent());
            // save the touch point position
            mDownTouchX = moveTouchX;
            mDownTouchY = moveTouchY;
          }
          break;
        case TouchEvent.CANCEL:
          mActivePointerId = INVALID_POINTER_ID;
          break;
      }
    }
    return true;
  }

  /**
   * 更新下一个View的宽高
   *
   * @param factor
   */
  private void updateNextItem(double factor) {
    if (componentContainer==null||componentContainer.getChildCount() < 2) {
      return;
    }
    if (factor > 1) {
      factor = 1;
    }
    Component nextView = componentContainer.getComponentAt(componentContainer.getChildCount() - 2);
    nextView.setScaleX((float) factor);
    nextView.setScaleY((float) factor);
  }

  private float getScrollProgressPercent() {
    if (movedBeyondLeftBorder()) {
      return -1f;
    } else if (movedBeyondRightBorder()) {
      return 1f;
    } else {
      float zeroToOneValue = (mPosX + mHalfWidth - leftBorder()) / (rightBorder() - leftBorder());
      return zeroToOneValue * 2f - 1f;
    }
  }

  private boolean resetCardViewOnStack() {
      float targetX = 0;
      float targetY = 0;
      boolean del = false;
    if (movedBeyondLeftBorder()) {
      // Left Swipe
        del = true;
      onSelected(true, getExitPoint(-mObjectW), 500);
      mFlingListener.onScroll(-1.0f);
    } else if (movedBeyondRightBorder()) {
      // Right Swipe
        del=true;
      onSelected(false, getExitPoint(mParentWidth), 500);
      mFlingListener.onScroll(1.0f);
    } else {
        float abslMoveDistance = Math.abs(mPosX - mObjectX);

        mFrame.createAnimatorProperty()
                .setDuration(500)
                .setCurveType(Animator.CurveType.OVERSHOOT)
                .moveToX(mObjectX)
                .moveToY(mObjectY)
                .rotate(0)
                .setStateChangedListener(new Animator.StateChangedListener() {
                    @Override
                    public void onStart(Animator animator) {

                    }

                    @Override
                    public void onStop(Animator animator) {

                    }

                    @Override
                    public void onCancel(Animator animator) {

                    }

                    @Override
                    public void onEnd(Animator animator) {

                    }

                    @Override
                    public void onPause(Animator animator) {

                    }

                    @Override
                    public void onResume(Animator animator) {

                    }
                })
                .start();
        mFlingListener.onScroll(0.0f);
        mPosX = 0;
        mPosY = 0;
        mDownTouchX = 0;
        mDownTouchY = 0;
        if (abslMoveDistance < 4.0) {
            mFlingListener.onClick(mDataObject);
        }
    }
    return false;
  }

  private boolean movedBeyondLeftBorder() {
    return mPosX + mHalfWidth < leftBorder();
  }

  private boolean movedBeyondRightBorder() {
    return mPosX + mHalfWidth > rightBorder();
  }

  public float leftBorder() {
    return mParentWidth / 4.f;
  }

  public float rightBorder() {
    return 3 * mParentWidth / 4.f;
  }

  public void onSelected(final boolean isLeft, float exitY, long duration) {
    mIsAnimationRunning = true;
    float exitX;
    if (!isLeft) {
      exitX = mParentWidth + getRotationWidthOffset();
    } else {
      exitX = -mObjectW - getRotationWidthOffset();
    }

    this.mFrame
        .createAnimatorProperty()
        .setDuration(duration)
        .setCurveType(Animator.CurveType.ACCELERATE)
        .moveByX(exitX)
        .moveByY(exitY)
        .setStateChangedListener(
            new Animator.StateChangedListener() {
              @Override
              public void onStart(Animator animator) {
              }

              @Override
              public void onStop(Animator animator) {
              }

              @Override
              public void onCancel(Animator animator) {
              }

              @Override
              public void onEnd(Animator animator) {
                if (!isLeft) {
                  mFlingListener.onCardExited();
                  mFlingListener.leftExit(mDataObject);
                } else {
                  mFlingListener.onCardExited();
                  mFlingListener.rightExit(mDataObject);
                }
                mIsAnimationRunning = false;
              }

              @Override
              public void onPause(Animator animator) {
              }

              @Override
              public void onResume(Animator animator) {
              }
            })
        .rotate(getExitRotation(!isLeft))
        .start();
  }

  /**
   * Starts a default left exit animation.
   */
  public void selectLeft() {
    if (!mIsAnimationRunning) {
      onSelected(true, mObjectY, 1000);
    }
  }

  /**
   * Starts a default right exit animation.
   */
  public void selectRight() {
    if (!mIsAnimationRunning) {
      onSelected(false, mObjectY, 1000);
    }
  }

  private float getExitPoint(int exitXPoint) {
    float[] x = new float[2];
    x[0] = mObjectX;
    x[1] = mPosX;

    float[] y = new float[2];
    y[0] = mObjectY;
    y[1] = mPosY;

    LinearRegression regression = new LinearRegression(x, y);

    // Your typical y = ax+b linear regression
    return (float) regression.slope() * exitXPoint + (float) regression.intercept();
  }

  private float getExitRotation(boolean isLeft) {
    float rotation = BASE_ROTATION_DEGREES * 2.f * (mParentWidth - mObjectX) / mParentWidth;
    if (mTouchPosition == TOUCH_BELOW) {
      rotation = -rotation;
    }
    if (isLeft) {
      rotation = -rotation;
    }
    return rotation;
  }

  /**
   * When the object rotates it's width becomes bigger.
   * The maximum width is at 45 degrees.
   * <p/>
   * The below method calculates the width offset of the rotation.
   *
   * @return rotation width offset
   */
  private float getRotationWidthOffset() {
    return mObjectW / MAX_COS - mObjectW;
  }

  public void setRotationDegrees(float degrees) {
    this.BASE_ROTATION_DEGREES = degrees;
  }

  public boolean isTouching() {
    return this.mActivePointerId != INVALID_POINTER_ID;
  }

  public Point getLastPoint() {
    return new Point(this.mPosX, this.mPosY);
  }

  protected interface FlingListener {
    void onCardExited();

    void leftExit(Object dataObject);

    void rightExit(Object dataObject);

    void onClick(Object dataObject);

    void onScroll(float scrollProgressPercent);
  }
}
